package org.fujene.communicate.blockwise;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import org.fujene.index.BlockwiseIndexSystem;
import org.fujene.memman.BlockwiseMemoryManager;
import org.fujene.search.BlockwiseSearchSystem;
import org.fujene.structure.Constants;
import org.fujene.structure.ContentInfo;
import org.fujene.structure.DataMatchingException;
import org.fujene.structure.ExtendQueue;
import org.fujene.structure.ExtendResult;
import org.fujene.structure.Node;
import org.fujene.structure.SearchQueue;
import org.fujene.term.Chopper;
import org.fujene.term.SenChopper;
import org.fujene.toolbox.CompareTool;
import org.msgpack.MessagePackObject;
import org.msgpack.object.ArrayType;
import org.msgpack.object.IntegerType;
import org.msgpack.object.RawType;
import org.msgpack.rpc.Client;
import org.msgpack.rpc.Future;
import org.msgpack.rpc.Server;
import org.msgpack.rpc.loop.EventLoop;

public class BlockwisePeerSystem{
	// MemoryManager -> Memory, Disk
	private BlockwiseMemoryManager memman = null;

	public boolean stopFlag = false;

	private ExecutorService execIndex = null;
	private ExecutorService execSearch = null;
	private ExecutorService execNodeLive = null;

	private String directory = null;
	private int blockNum = 0;
	private int blockSize = 0;

	// //////// P2P ///////////
	private static final int PEERPORT = Constants.PEER_PORT;

	private int replication;
	private int indexType;

	private TreeSet<Node> nodes = null;
	private Node myNode = null;

	private String[] firstHost = null;

	private Server server = null;

	private Chopper chopper = null;

	private ContentInfo contentInfo = null;

	// //////// Logger ///////////
	private Logger mainLogger = null;

	public void initialize() throws IOException, InterruptedException, DataMatchingException{
		nodes = new TreeSet<Node>();

		// 自分のIPアドレス取得
		Enumeration<NetworkInterface> enulfs = NetworkInterface.getNetworkInterfaces();
		if(!enulfs.hasMoreElements()) return;
		NetworkInterface ni = enulfs.nextElement();

		Enumeration<InetAddress> enuAddr = ni.getInetAddresses();
		if(!enuAddr.hasMoreElements()) return;
		InetAddress addrInfo = enuAddr.nextElement();
		// TODO: とりあえずIPv4のみ使うことにする ほんとはIPv6使いたいね
		while(!(addrInfo instanceof Inet4Address)){
			addrInfo = enuAddr.nextElement();
		}

		long hash = calcHash(addrInfo);
		String address = addrInfo.getHostAddress();
		myNode = new Node(address, hash);
		nodes.add(myNode);
		getInformation();

		if(contentInfo != null)
			contentInfo.optimize();
		else contentInfo = new ContentInfo(new String[]{ "Content" }, new String[0]);

		server = new Server(EventLoop.defaultEventLoop());
		server.serve(new BlockwisePeerHandler(mainLogger, this));
		server.listen(PEERPORT);

		new BlockwiseSearchSystem(this, mainLogger);

		new BlockwiseIndexSystem(this, contentInfo.getContentNum(), contentInfo.getAppendixNum(),
				mainLogger);

		memman = new BlockwiseMemoryManager(blockSize, blockNum, directory, mainLogger, contentInfo, this);

		execIndex = Executors.newFixedThreadPool(1);

		execSearch = Executors.newFixedThreadPool(1);

		execNodeLive = Executors.newFixedThreadPool(2);

		chopper = new SenChopper();
		execNodeLive.execute(new SurvivingService());
		if(firstHost != null){
			registerNode();
			callForData();
		}
	}

	private boolean getArgs(String[] args){
		boolean isEnded = false;
		String settingFile;

		if(/* args.length > 2 || */args.length == 1){
			System.err.println("Usage: PeerSystem [--setting <SettingFile>] [--secondary <IPAddress ...>]");
			return isEnded;
		}
		// デフォルト値
		this.blockNum = 256;
		this.blockSize = 1048576;
		this.directory = "./fujene_data/";
		this.indexType = 1;
		this.replication = 1;
		this.mainLogger = Logger.getLogger("Fujene search engine");
		try{
			FileHandler handle = new FileHandler("fujene_data/fujene.log");
			handle.setFormatter(new SimpleFormatter());
			this.mainLogger.addHandler(handle);
		}
		catch(SecurityException e1){
			e1.printStackTrace();
		}
		catch(Exception e1){
			e1.printStackTrace();
		}
		this.mainLogger.setLevel(Level.ALL);

		// ローカル
		ArrayList<String> contentName = new ArrayList<String>();
		ArrayList<String> supplementName = new ArrayList<String>();
		int count = 0;

		if(args.length > 1 && args[0].equals("--setting")){
			settingFile = args[1];

			BufferedReader br = null;
			String input = null;
			try{
				br = new BufferedReader(new FileReader(new File(settingFile)));

				while((input = br.readLine()) != null){
					// "#"始まりはコメント行
					if(!input.startsWith("#")){
						String[] settingArgs = input.split("=");
						if(settingArgs.length >= 2){
							if(settingArgs[0].equalsIgnoreCase("BlockNum"))
								blockNum = Integer.parseInt(settingArgs[1]);
							else if(settingArgs[0].equalsIgnoreCase("BlockSize"))
								blockSize = Integer.parseInt(settingArgs[1]);
							else if(settingArgs[0].equalsIgnoreCase("Directory"))
								directory = settingArgs[1];
							else if(settingArgs[0].equalsIgnoreCase("PeerMode")){
								if(settingArgs[1].equalsIgnoreCase("on")){
									replication = 3;
								}
							}else if(settingArgs[0].equalsIgnoreCase("replication"))
								replication = Integer.parseInt(settingArgs[1]);
							else if(settingArgs[0].equalsIgnoreCase("IndexMode"))
								indexType = Integer.parseInt(settingArgs[1]);
							else if(settingArgs[0].equalsIgnoreCase("LogLevel")){
								try{
									mainLogger.setLevel(Level.parse(settingArgs[1]));
								}
								catch(Exception e){
									e.printStackTrace();
									mainLogger.severe("Specified log-level does not exist.");
								}
							}else if(settingArgs[0].equalsIgnoreCase("Content")){
								contentName.add(settingArgs[1]);
							}else if(settingArgs[0].equalsIgnoreCase("Supplement")){
								supplementName.add(settingArgs[1]);
							}else if(settingArgs[0].equalsIgnoreCase("Connection")){
								// この時点で紐付け情報を作る
								if(contentInfo == null)
									contentInfo = new ContentInfo((String[])contentName.toArray(),
											(String[])supplementName.toArray());

								String[] values = settingArgs[1].split(",");
								int[] indices = new int[values.length];
								for(int i = 0; i < indices.length; i++)
									indices[i] = Integer.parseInt(values[i]);
								contentInfo.connectGraph(count++, indices);
							}
						}
					}

					isEnded = true;
				}
			}
			catch(FileNotFoundException e){
				mainLogger.severe("Please input appropriate file. Search system will shutdown.");
				return isEnded;
			}
			catch(IOException e){}
			finally{
				if(br != null){
					try{
						br.close();
					}
					catch(IOException e){}
				}
			}
		}else if(args.length == 0){
			isEnded = true;
		}

		// セカンダリモード
		if(args.length > 1 && args[0].equals("--secondary")){
			firstHost = new String[args.length - 1];
			for(int i = 1; i < args.length; i++){
				firstHost[i - 1] = args[i];
			}
			isEnded = true;
		}

		return isEnded;
	}

	// ローカルで動かしたいときはこっち
	public static void main(String[] args) throws InterruptedException, IOException, DataMatchingException{
		BlockwisePeerSystem mainSystem = new BlockwisePeerSystem();

		if(!mainSystem.getArgs(args)) return;

		mainSystem.initialize();

		mainSystem.console();
	}

	private void console() throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String arg;

		try{
			Thread.sleep(700);
		}
		catch(InterruptedException ignored){}

		while(true){
			System.out.print("Fujene > ");
			arg = br.readLine();

			String[] args = arg.split(" ");
			if(args.length > 0){
				if(args[0].equals("index") || args[0].equals("i")){
					if(args.length == 1){
						System.out.println("Usage: index ID <Contents> [<Supplements>]");
						System.out.println(contentInfo.getContentNum() + " contents, "
								+ contentInfo.getAppendixNum() + " supplements are needed.");
					}else{
						if(args.length < contentInfo.getContentNum() + contentInfo.getAppendixNum() + 2){
							System.out.println("Input appropriate contents.");
						}else{
							setNewIndexExtent(new ExtendQueue(Arrays.copyOfRange(args, 2,
									2 + contentInfo.getContentNum()), Arrays.copyOfRange(args,
									2 + contentInfo.getContentNum(), 2 + contentInfo.getContentNum()
											+ contentInfo.getAppendixNum()), Long.parseLong(args[1])));
						}
					}
				}else if(args[0].equals("search") || args[0].equals("s")){
					if(args.length < 3)
						System.out.println("Specify query.");
					else{
						try{
							ExtendQueue queue;
							int i, j;
							ExtendResult result = setNewSearch(new SearchQueue(args[1], args[2], 0, 1000000));
							// ちょっとなら結果を出す
							System.out.println("Result: " + result.getLength() + " articles.");
							if(result.getLength() <= 10){
								for(i = 0; i < result.getLength(); i++){
									System.out.print(i + ": ");
									queue = result.getQueue(i);
									for(j = 0; j < queue.contents.length; j++){
										System.out.print(contentInfo.getContentName(j) + " / ");
										System.out.println(queue.contents[j]);
									}
								}
							}
						}
						catch(Exception e){
							e.printStackTrace();
						}
					}
				}else if(args[0].equals("searchspec") || args[0].equals("p")){
					if(args.length != 4)
						System.out.println("Specify arguments.");
					else{
						try{
							ExtendResult result = setNewSearch(new SearchQueue(args[1], args[2],
									Integer.parseInt(args[3]), Integer.parseInt(args[4])));
							int i, j;
							ExtendQueue queue;
							System.out.println("Result total: " + result.getLength());
							if(result.getLength() <= 10){
								for(i = 0; i < result.getLength(); i++){
									System.out.println(i + ": ");
									queue = result.getQueue(i);
									for(j = 0; j < queue.contents.length; j++){
										System.out.print(contentInfo.getContentName(j) + " / ");
										System.out.println(queue.contents[j]);
									}
								}
							}
						}
						catch(Exception ignored){
							ignored.printStackTrace();
						}
					}
				}else if(args[0].equals("indexcount") || args[0].equals("c")){
					System.out.println("Total count: " + memman.getCounter());
				}else if(args[0].equals("querycount") || args[0].equals("u")){
					System.out.println("Total count: " + memman.getSearchcounter());
				}else if(args[0].equals("termcount") || args[0].equals("t")){
					System.out.println("Term count: " + memman.getTerms());
				}else if(args[0].equals("skipcount") || args[0].equals("k")){
					System.out.println("Skip ptr. count: " + memman.getSkipPointerLen());
				}else if(args[0].equals("memory") || args[0].equals("m")){
					memman.getMemoryList();
				}else if(args[0].equals("nodes") || args[0].equals("n")){
					showNodes();
				}else if(args[0].equals("save") || args[0].equals("v")){
					saveMemory();
				}else if(args[0].equals("read") || args[0].equals("r")){
					readMemory();
				}else if(args[0].equals("quit") || args[0].equals("q")){
					setStopper(true);
				}else{
					System.out
							.println("Command: <i>ndex <args>, <s>earch <args> type, searchs<p>ec <args> from length, <t>ermcount, index<c>ount, q<u>erycount, s<k>ipcount, <m>emory, <r>ead, sa<v>e, <n>ode, <q>uit");
				}
			}
		}
	}

	// 外部からJUnitとかで呼び出すときはこっち
	public void testMain(String[] args) throws InterruptedException, IOException, DataMatchingException{
		this.blockNum = 32;
		this.blockSize = 1048576;
		this.directory = "./testDir/";
		this.initialize();
	}

	// 全部の動作を止める操作
	public void setStopper(boolean isStop){
		stopFlag = isStop;
		execIndex.shutdownNow();
		execSearch.shutdownNow();
		// 無理やりな気がする
		Runtime.getRuntime().exit(0);
	}

	public void setNewIndexExtent(ExtendQueue newIndex){
		execIndex.execute(new PeerIndexContentsHost(newIndex));
	}

	public class PeerIndexContentsHost implements Runnable{
		private ExtendQueue newIndex;

		public PeerIndexContentsHost(ExtendQueue index){
			this.newIndex = index;
		}

		@Override
		public void run(){
			memman.index(newIndex, chopper);
		}
	}

	// 大元のsearch窓口
	public ExtendResult setNewSearch(SearchQueue newQuery) throws Exception{
		return memman.search(newQuery, chopper);
	}

	public void readMemory(){
		memman.initializeMemory();
	}

	public void saveMemory(){
		memman.saveMemory();
	}

	// ///////////////////// P2P関連の実装 //////////////////////
	// ノードを返します
	public TreeSet<Node> getNode(){
		return nodes;
	}

	public ArrayType getInfo(){
		MessagePackObject[] objs = new MessagePackObject[6 + contentInfo.getContentNum()
				+ contentInfo.getAppendixNum()];

		objs[0] = IntegerType.create(blockNum);
		objs[1] = IntegerType.create(blockSize);
		objs[2] = IntegerType.create(replication);
		objs[3] = IntegerType.create(indexType);
		objs[4] = IntegerType.create(contentInfo.getContentNum());
		objs[5] = IntegerType.create(contentInfo.getAppendixNum());
		for(int i = 0; i < contentInfo.getContentNum(); i++){
			objs[6 + i] = RawType.create(contentInfo.getContentName(i));
		}
		for(int i = 0; i < contentInfo.getAppendixNum(); i++){
			objs[6 + contentInfo.getContentNum() + i] = RawType.create(contentInfo.getAppendixName(i));
		}
		return ArrayType.create(objs);
	}

	public int getQueueLength(int type){
		if(type == 0)
			return ((ThreadPoolExecutor)execIndex).getPoolSize();
		else if(type == 1)
			return ((ThreadPoolExecutor)execSearch).getPoolSize();
		else return -1;
	}

	public byte[] callGetFunc(String func, int blockIndex, byte[] argbytes, boolean atOnce){
		Node callNode = getNodeByIndex(blockIndex);
		Client[] clients = new Client[(nodes.size() < replication ? nodes.size() : replication)];
		@SuppressWarnings("unchecked") Future<MessagePackObject>[] future = new Future[(nodes.size() < replication ? nodes
				.size() : replication)];
		MessagePackObject val;
		byte[] valb = null;

		try{
			for(int i = 0; i < (nodes.size() < replication ? nodes.size() : replication); i++){
				if(!callNode.equals(myNode)){
					clients[i] = new Client(callNode.getAddr(), PEERPORT);
				}
				callNode = getPrev(callNode);
			}
		}
		catch(UnknownHostException e){}

		for(int i = 0; i < clients.length; i++){
			if(clients[i] != null){
				future[i] = clients[i].callAsyncApply("callGetFunc", new Object[]{ RawType.create(func),
						RawType.create(argbytes) });
			}
		}

		try{
			for(int i = 0; i < clients.length; i++){
				if(future[i] != null){
					val = future[i].get();
					if(valb == null && val.isRawType()){
						valb = val.asByteArray();
					}
				}
			}
		}
		catch(InterruptedException e){}

		for(int i = 0; i < (nodes.size() < replication ? nodes.size() : replication); i++){
			if(clients[i] != null) clients[i].close();
		}

		return valb;
	}

	public byte[] callSetFunc(String func, int blockIndex, byte[] argbytes, boolean atOnce){
		Node callNode = getNodeByIndex(blockIndex);
		Client[] clients = new Client[(nodes.size() < replication ? nodes.size() : replication)];
		@SuppressWarnings("unchecked") Future<MessagePackObject>[] future = new Future[(nodes.size() < replication ? nodes
				.size() : replication)];
		MessagePackObject val;
		byte[] valb = null;

		try{
			for(int i = 0; i < (nodes.size() < replication ? nodes.size() : replication); i++){
				if(!callNode.equals(myNode)){
					clients[i] = new Client(callNode.getAddr(), PEERPORT);
				}
				callNode = getPrev(callNode);
			}
		}
		catch(UnknownHostException e){}

		for(int i = 0; i < clients.length; i++){
			if(clients[i] != null){
				future[i] = clients[i].callAsyncApply("callSetFunc", new Object[]{ RawType.create(func),
						RawType.create(argbytes) });
			}
		}

		try{
			for(int i = 0; i < clients.length; i++){
				if(future[i] != null){
					val = future[i].get();
					if(valb == null && val.isRawType()){
						valb = val.asByteArray();
					}
				}
			}
		}
		catch(InterruptedException e){}

		for(int i = 0; i < (nodes.size() < replication ? nodes.size() : replication); i++){
			if(clients[i] != null) clients[i].close();
		}

		return valb;
	}

	public byte[] invokeGetFunc(String func, byte[] argbytes){
		return memman.invokeGetFunc(func, argbytes);
	}

	public byte[] invokeSetFunc(String func, byte[] argbytes){
		return memman.invokeSetFunc(func, argbytes);
	}

	public int getBlockType(int blockIndex){
		return memman.getBlockType(blockIndex);
	}

	public int getContentType(int blockIndex){
		return memman.getContentType(blockIndex);
	}

	public byte[] getBlock(int blockIndex){
		return memman.getBlock(blockIndex);
	}

	public void pushBlock(int blockIndex, int blockType, int contentIndex, boolean isAllocate){
		memman.pushBlock(blockIndex, blockType, contentIndex, isAllocate);
	}

	public void setBlock(int blockIndex, int blockType, int contentIndex){
		Node eachNode = null;
		Client client;
		boolean isStoreLocal = false;

		try{
			for(int i = 0; i < nodes.size(); i++){
				eachNode = getNodeByIndex(i);
				if(!eachNode.equals(myNode)){
					isStoreLocal = CompareTool.isStoreLocally(blockIndex, nodes.size(), i, replication);

					client = new Client(eachNode.getAddr(), PEERPORT);

					client.callApply("setBlockInfo", new Object[]{ blockIndex, blockType, contentIndex,
							isStoreLocal });

					client.close();
				}
			}
		}
		catch(UnknownHostException e){
			mainLogger.severe("Node cannot found. " + eachNode.getAddr());
		}
	}

	// 自分のハッシュ値を計算する
	private long calcHash(InetAddress addrInfo){
		// とりあえずマシン名で作る
		long returnHash = addrInfo.getHostName().hashCode() * 0x123456789ABCDEFL;
		returnHash *= returnHash;
		Long.rotateLeft(returnHash, 42);

		return returnHash;
	}

	// n個前(hashが小さい方)のピアを取得
	private Node getDescentHash(Node from, int distance){
		if(from == null) return null;
		Node getNode = from;

		for(int i = 0; i < distance; i++){
			getNode = getPrev(getNode);
		}

		return getNode;
	}

	// n個後(hashが大きい方)のピアを取得
	private Node getAscentHash(Node from, int distance){
		if(from == null) return null;
		Node getNode = from;

		for(int i = 0; i < distance; i++){
			getNode = getNext(getNode);
		}

		return getNode;
	}

	// 次のピアを取得
	private Node getNext(Node from){
		if(from == null) return null;
		TreeSet<Node> calcNode = new TreeSet<Node>(nodes);
		boolean isNext = false;

		for(Node eachNode: calcNode){
			if(isNext) return eachNode;
			if(eachNode.equals(from)) isNext = true;
		}

		return calcNode.first();
	}

	// 前のピアを取得
	private Node getPrev(Node from){
		if(from == null) return null;
		TreeSet<Node> calcNode = new TreeSet<Node>(nodes);
		Node prevNode = null;

		for(Node eachNode: calcNode){
			if(eachNode.equals(from)) if(prevNode == null)
				return calcNode.last();
			else return prevNode;
			prevNode = eachNode;
		}

		return null;
	}

	private int getNodeIndex(Node node){
		int count = 0;
		for(Node eachNode: nodes){
			if(eachNode.equals(node)) return count;
			count++;
		}

		return -1;
	}

	// 数字でノードを指定
	private Node getNodeByIndex(int index){
		int modulo = index % nodes.size();
		int count = 0;

		for(Node anyNode: nodes){
			if(count++ == modulo) return anyNode;
		}

		return null;
	}

	// indexable状態を設定
	public void setIndexable(long hash){
		Node specified = nodes.ceiling(new Node(null, hash));

		specified.indexable();
	}

	// indexable状態を設定
	public void setSearchable(long hash){
		Node specified = nodes.ceiling(new Node(null, hash));

		specified.searchable();
	}

	// サークル情報を取得します。
	private void getInformation(){
		if(firstHost != null){
			Client client = null;
			for(String hosts: firstHost){
				try{
					client = new Client(hosts, PEERPORT);
					client.setRequestTimeout(3);

					// 情報取得
					MessagePackObject infoobj = client.callApply("getInfos", new Object[]{});
					MessagePackObject[] infos;
					if(infoobj.isArrayType()){
						infos = infoobj.asArray();
						blockNum = infos[0].asInt();
						blockSize = infos[1].asInt();
						replication = infos[2].asInt();
						indexType = infos[3].asInt();
						int contentNum = infos[4].asInt();
						String[] strcontent = new String[contentNum];
						int supplementNum = infos[5].asInt();
						String[] strsupp = new String[supplementNum];
						for(int i = 0; i < contentNum; i++){
							strcontent[i] = infos[6 + i].asString();
						}
						for(int i = 0; i < supplementNum; i++){
							strsupp[i] = infos[6 + contentNum + i].asString();
						}
						contentInfo = new ContentInfo(strcontent, strsupp);
					}else return;

					break;
				}
				catch(UnknownHostException e){
					continue;
				}
			}
		}
	}

	// 追加操作を行います。
	public void addReplication(Node newPeer){
		// サークルに追加
		nodes.add(newPeer);

		try{
			callForData();
		}
		catch(DataMatchingException e){}
		catch(IOException e){}
	}

	private void showNodes(){
		TreeSet<Node> showNode = new TreeSet<Node>(nodes);
		int count = 0;

		for(Node eachNode: showNode){

			System.out.println((count++) + ": Hash-" + String.format("0x%016x", eachNode.getHash())
					+ " Address-" + eachNode.getAddr());
		}
	}

	// ノードを登録します。
	private void registerNode(){
		MessagePackObject obj;
		Client client;
		MessagePackObject[] objs;

		for(String host: firstHost){
			// ノードリスト要求
			try{
				client = new Client(host, PEERPORT);
				client.setRequestTimeout(5);

				obj = client.callApply("getNodeList", new Object[]{});
				if(obj.isArrayType()){
					objs = obj.asArray();
					for(int i = 0; i < objs.length; i += 2)
						nodes.add(new Node(objs[i].asString(), objs[i + 1].asLong()));
				}else{
					// やり直し
					client.close();
					nodes.clear();
					continue;
				}
				// ブロックリスト受信
				obj = client.callApply("blockList", new Object[]{});
				if(obj.isArrayType()){
					objs = obj.asArray();
					for(int i = 0; i < objs.length / 2; i++){
						memman.registerBlock(i, objs[2 * i + 1].asInt(), objs[2 * i].asInt());
					}
				}

				client.close();
			}
			catch(UnknownHostException e){
				continue;
			}

			// 現時点でのノードリストを使って自分のことを送信
			TreeSet<Node> currentNode = new TreeSet<Node>(nodes);
			Client nodeClient;
			System.out.println("My node: " + myNode.getAddr());
			for(Node eachNode: currentNode){
				System.out.println("Node: " + eachNode.getAddr());
				if(!eachNode.equals(myNode)){
					try{
						nodeClient = new Client(eachNode.getAddr(), PEERPORT);
						nodeClient.setRequestTimeout(5);

						nodeClient.callApply("register", new Object[]{ RawType.create(myNode.getAddr()),
								IntegerType.create(myNode.getHash()), });

						nodeClient.close();
					}
					catch(UnknownHostException e){}
				}
			}
			break;
		}
	}

	// データ要求をします。
	private void callForData() throws DataMatchingException, IOException{
		MessagePackObject obj;
		MessagePackObject[] objs;
		Client client;
		int modulo;
		int i;
		int copyOffset;
		int nodelen = nodes.size();
		Node node;

		// 範囲の計算
		if(nodes.size() <= replication)
			modulo = 0;
		else modulo = getNodeIndex(myNode);

		mainLogger.finest("Modulo: " + modulo);

		for(i = 0, copyOffset = 1; i < memman.getBlockAmount(); i += nodelen, copyOffset++){
			if(copyOffset == nodelen) copyOffset = 1;
			for(int j = 0; j < nodelen && i + j < memman.getBlockAmount(); j++){
				if((j >= modulo && j < modulo + replication) || (j < ((modulo + replication) - nodelen))){
					if(!memman.existsLocal(i + j)){
						mainLogger.finest("Copy " + (i + j));

						node = getAscentHash(getNodeByIndex(0), copyOffset);
						// if (!node.isIndexable()) {
						// // 新しいノードは飛ばす
						// if (++copyOffset == nodelen)
						// copyOffset = 1;
						// node = getAscentHash(myNode, copyOffset);
						// }
						client = new Client(node.getAddr(), PEERPORT);

						obj = client.callApply("getBlock", new Object[]{ (i + j) });
						if(obj.isArrayType()){
							objs = obj.asArray();
							memman.setBlock(objs[0].asByteArray(), i + j, objs[1].asInt(), objs[2].asInt());
						}

						client.close();
					}
				}else{
					if(memman.existsLocal(i + j)){
						mainLogger.finest("Delete " + (i + j));
						memman.deleteBlock(i + j);
					}
				}
			}
		}

		// TODO: コピー終了時点でindexableにする

	}

	// 削除操作を行います。
	public void deleteReplication(Node delPeer) throws DataMatchingException, IOException{
		MessagePackObject obj;
		MessagePackObject[] objs;
		Client client;
		int modulo;
		int i;
		int copyOffset;
		int nodelen = nodes.size() - 1;
		// ノードテーブルから削除する
		nodes.remove(delPeer);

		Node node;

		delPeer.setDead();

		// 範囲の計算
		if(nodes.size() <= replication)
			modulo = 0;
		else modulo = getNodeIndex(myNode);

		for(i = 0, copyOffset = 1; i < memman.getBlockAmount(); i += nodelen, copyOffset++){
			if(copyOffset == nodelen) copyOffset = 1;
			for(int j = 0; j < nodelen; j++){
				if((j >= modulo && j < modulo + replication) || (j < ((modulo + replication) - nodelen))){
					if(!memman.existsLocal(i + j)){
						node = getDescentHash(getNodeByIndex(0), copyOffset);
						if(node.equals(delPeer)){
							// 死亡したノードは飛ばす
							node = getPrev(node);
						}
						client = new Client(node.getAddr(), PEERPORT);

						obj = client.callApply("getBlock", new Object[]{ (i + j) });
						if(obj.isArrayType()){
							objs = obj.asArray();
							memman.setBlock(objs[0].asByteArray(), i + j, objs[1].asInt(), objs[2].asInt());
						}

						client.close();
					}
				}
			}
		}

		// ノードテーブルから削除する
		nodes.remove(delPeer);
	}

	public boolean storeLocally(int blockIndex){
		int modulo;
		int nodelen = nodes.size();

		// 範囲の計算
		if(nodes.size() <= replication)
			return true;
		else modulo = getNodeIndex(myNode);

		if(((blockIndex % nodelen) >= modulo && (blockIndex % nodelen) < modulo + replication)
				|| ((blockIndex % nodelen) < ((modulo + replication) - nodelen))) return true;
		return false;
	}

	public int[] getBlockList(){
		Integer[] blockArray = memman.getBlockList();
		int[] values = new int[blockArray.length];

		for(int i = 0; i < blockArray.length; i++){
			values[i] = blockArray[i].intValue();
		}

		return values;
	}

	/**
	 * ノードの生存確認をします。
	 */
	public class SurvivingService implements Runnable{
		public SurvivingService(){}

		@Override
		public void run(){
			while(true){
				Client client;
				MessagePackObject result;
				TreeSet<Node> checker = new TreeSet<Node>(nodes);

				for(Node eachInfo: checker){
					if(!eachInfo.equals(myNode)){
						try{
							client = new Client(eachInfo.getAddr(), PEERPORT);
							// 3秒だけ待ってやる
							// client.setRequestTimeout(3);

							result = client.callApply("isLive", new Object[]{});
							if(!result.isRawType()){
								// クライアント死亡と推定
								deleteReplication(eachInfo);
							}else if(stopFlag){
								// 自分が死ぬことを通知
								client.callApply("delete", new Object[]{});
							}
						}
						catch(Exception e){
							mainLogger.info("Node " + eachInfo.getAddr() + " seems to be dead.");
							// クライアント死亡と推定
							try{

								deleteReplication(eachInfo);
							}
							catch(DataMatchingException e1){}
							catch(IOException e1){}
						}
					}
				}

				try{
					if(stopFlag){
						break;
					}
					TimeUnit.SECONDS.sleep(60);
				}
				catch(Exception e){
					continue;
				}
			}
		}
	}
}
